En este documento se pretende analizar y visualizar un conjunto de 267,157 observaciones de crímenes de diferentes categorías ocurridos en los municipios de GUADALAJARA, SAN PEDRO TLAQUEPAQUE, TLAJOMULCO DE ZÚÑIGA, TONALÁ y ZAPOPAN.
Estos datos fueron obtenidos del IEEG con registros desde el 01/01/2017 hasta el 01/01/2025.
Los pasos que seguiremos serán los siguientes:
En el bloque siguiente se cargarán las librerías necesarias para llevar a cabo el ejercicio.
# install.packages("tidyverse") # Lectura csv y manejo de strings
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# install.packages("readr") # Función parse_time para las fechas
library(readr)
# install.packages("dplyr") # Funciones para manipulación de datos
library(dplyr)
# install.packages("missForest") # Imputación de datos faltantes
library(missForest)
# install.packages("hms") # Conversor de segundos a horas
library(hms)
##
## Adjuntando el paquete: 'hms'
##
## The following object is masked from 'package:lubridate':
##
## hms
# install.packages("lubridate") # Manejo de fechas
library(lubridate)
# install.packages("xgboost") # Modelos de machine learning
library(xgboost)
##
## Adjuntando el paquete: 'xgboost'
##
## The following object is masked from 'package:dplyr':
##
## slice
# install.packages("ggplot2") # Visualización de datos
library(ggplot2)
# install.packages("leaflet") # Mapas interactivos
library(leaflet)
# install.packages("sf") # Manejo de datos espaciales
library(sf)
## Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
# install.packages("randomForest") # Modelos de random forest
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
##
## Adjuntando el paquete: 'randomForest'
##
## The following object is masked from 'package:dplyr':
##
## combine
##
## The following object is masked from 'package:ggplot2':
##
## margin
# install.packages("caret") # Utilidades para machine learning
library(caret)
## Cargando paquete requerido: lattice
##
## Adjuntando el paquete: 'caret'
##
## The following object is masked from 'package:purrr':
##
## lift
# install.packages("stringr") # Manipulación de cadenas
library(stringr)
# install.packages("leafgl") # Visualización eficiente en leaflet
library(leafgl)
Una vez descargado el archivo de la Plataforma de Seguridad IEEG, y colocado en nuestro directorio de trabajo, procedemos a cargarlo.
data <- read_csv(
"delitos.csv", # Nombre del archivo
na = c("NA", "N.D.", "NO DISPONIBLE"), # Cadenas que se trataran como valores NULL
show_col_types = FALSE # No se mostrara el resultado en consola.
)
summary(data) # Muestra un resumen de los datos.
## fecha delito x y
## Min. :2017-01-01 Length:267157 Min. :-103.6 Min. :20.39
## 1st Qu.:2018-07-27 Class :character 1st Qu.:-103.4 1st Qu.:20.61
## Median :2020-06-01 Mode :character Median :-103.4 Median :20.65
## Mean :2020-08-31 Mean :-103.4 Mean :20.65
## 3rd Qu.:2022-10-10 3rd Qu.:-103.3 3rd Qu.:20.69
## Max. :2025-01-01 Max. :-103.1 Max. :20.96
## NA's :13008 NA's :13008
## colonia municipio clave_mun hora
## Length:267157 Length:267157 Min. : 39.0 Length:267157
## Class :character Class :character 1st Qu.: 39.0 Class :character
## Mode :character Mode :character Median : 97.0 Mode :character
## Mean : 81.5
## 3rd Qu.:101.0
## Max. :120.0
##
## bien_afectado zona_geografica
## Length:267157 Length:267157
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
Se puede observar en el resumen de datos que hora es de
formato character, cuando este debe ser de tipo
tiempo o numérico, se
verificó la columna y se encontraron celdas
con terminación PM, AM, y espacios
en blanco y se procede a su limpieza y acomodo.
Para después, convertirlos en valores de tipo
numérico.
También se realizará una factorización de las variables a
categorías, para eficientizar la memoria y
evitar la repetición de datos así como a ayudar a modelos
predictivos.
# Correccion de hora
data <- data %>%
mutate(
# Transformacion de valores
hora = hora %>% # Se asiga a la misma columna
str_trim() %>% # Eliminamos espacios vacios.
str_replace_all(";", ":") %>% # Quitamos valores espesificos
str_replace_all("O", "0") %>% # Corregimos errores de registo
str_remove_all(",") %>%
str_remove_all("\\s*(a\\.m\\.|p\\.m\\.)") %>% # Limpiamos los valores am y pm
str_extract("\\d{1,2}:\\d{2}"),
# Damos un formato adecuado
)
# Convertimos a formatos acorde al tipo de dato.
data <- data %>%
mutate(
fecha = as.Date(fecha),
hora = parse_time(hora, "%H:%M")
)
# Factorizacion de las columnas
data <- data %>%
mutate(across(
c(delito, colonia, municipio, clave_mun, bien_afectado), # Columnas a convertie en factor.
as.factor
))
summary(data) # Verificamos los datos.
## fecha delito x
## Min. :2017-01-01 Robo a vehiculos particulares:85220 Min. :-103.6
## 1st Qu.:2018-07-27 Violencia familiar :67459 1st Qu.:-103.4
## Median :2020-06-01 Lesiones dolosas :41702 Median :-103.4
## Mean :2020-08-31 Robo casa habitacion :27439 Mean :-103.4
## 3rd Qu.:2022-10-10 Robo de motocicleta :16556 3rd Qu.:-103.3
## Max. :2025-01-01 Abuso sexual infantil :16269 Max. :-103.1
## (Other) :12512 NA's :13008
## y colonia municipio
## Min. :20.39 ZONA CENTRO : 3450 GUADALAJARA :99296
## 1st Qu.:20.61 CENTRO : 2847 SAN PEDRO TLAQUEPAQUE:37232
## Median :20.65 HACIENDA SANTA FE: 2417 TLAJOMULCO DE ZUÑIGA :37076
## Mean :20.65 OBLATOS : 2412 TONALA :29976
## 3rd Qu.:20.69 CHULAVISTA E1 : 2230 ZAPOPAN :63577
## Max. :20.96 (Other) :237280
## NA's :13008 NA's : 16521
## clave_mun hora
## 39 :99296 Min. :00:00:00.000000
## 97 :37076 1st Qu.:07:05:00.000000
## 98 :37232 Median :14:30:00.000000
## 101:29976 Mean :13:20:52.774186
## 120:63577 3rd Qu.:18:25:00.000000
## Max. :23:59:00.000000
## NA's :75544
## bien_afectado zona_geografica
## El patrimonio :129215 Length:267157
## La familia : 67459 Class :character
## La libertad y la seguridad sexual: 19045 Mode :character
## La vida y la integridad corporal : 51438
##
##
##
Debido a que estos datos se encuentran en una zona geográfica específica ZMG (Zona Metropolitana de Guadalajara), es una columna que nos es irrelevante pues todos los datos la tienen, así que para reducir datos procederemos a la limpieza de esa columna así como a la limpieza de los datos con NA correlacionados, por ejemplo:
Tenemos datos que no tienen colonia ni coordenada, si bien pudiéramos dar una solución, posiblemente mediante un modelo de imputación como missForest, que nos ubique los delitos vacíos en la colonia más probable, no es el fin de este ejemplo, así que se decide suprimirlos.
Pero para fines de que el lector vea una implementación de missForest, se hará una media con la hora, para eliminar los valores NULL.
# Apartir de aqui, crearemos cambiaremos la variable data, para dejar al lector, la posibilidad de implementar el modelo de predicion de colonia con los datos originales.
# Extraer observaciones, donde no hay colonia y tampoco cordenada x e y.
cleanData <- data %>%
filter(!is.na(colonia) & !is.na(x) & !is.na(y))
# Extraemos la columna zona geografica
cleanData <- cleanData %>% select(-zona_geografica)
# Imputacion de la hora con missForest
imp <- cleanData %>% filter(!is.na(hora)) %>% # Quitamos los valores NA
mutate(hora_seg = as.numeric(hora)) %>% # cambiamos el formato de la hora, para ayudar al algoritmo
select(hora_seg, delito, municipio, bien_afectado) # Extraemos las columnas para el entrenamiento.
set.seed(123) # Para que el leector pueda replicar los datos.
model <- missForest(imp)
## Warning in mean.default(xmis[, t.co], na.rm = TRUE): argument is not numeric or
## logical: returning NA
# Reemplaza en tu dataset original
cleanData$hora <- ifelse(is.na(cleanData$hora),
as_hms(model$ximp$hora_seg),
cleanData$hora)
cleanData$hora <- as_hms(cleanData$hora)
summary(cleanData) # verificamos
## fecha delito x
## Min. :2017-01-01 Robo a vehiculos particulares:82498 Min. :-103.6
## 1st Qu.:2018-08-16 Violencia familiar :63044 1st Qu.:-103.4
## Median :2020-06-23 Lesiones dolosas :39256 Median :-103.4
## Mean :2020-09-12 Robo casa habitacion :25915 Mean :-103.4
## 3rd Qu.:2022-10-09 Robo de motocicleta :15925 3rd Qu.:-103.3
## Max. :2025-01-01 Abuso sexual infantil :12532 Max. :-103.1
## (Other) :11466
## y colonia municipio
## Min. :20.40 ZONA CENTRO : 3450 GUADALAJARA :96148
## 1st Qu.:20.61 CENTRO : 2847 SAN PEDRO TLAQUEPAQUE:35825
## Median :20.65 HACIENDA SANTA FE: 2417 TLAJOMULCO DE ZUÑIGA :31257
## Mean :20.65 OBLATOS : 2412 TONALA :27706
## 3rd Qu.:20.69 CHULAVISTA E1 : 2230 ZAPOPAN :59700
## Max. :20.86 SAN ANDRES : 1655
## (Other) :235625
## clave_mun hora
## 39 :96148 Min. :00:00:00.000000
## 97 :31257 1st Qu.:07:30:00.000000
## 98 :35825 Median :14:00:00.000000
## 101:27706 Mean :13:21:28.295536
## 120:59700 3rd Qu.:18:40:00.000000
## Max. :23:59:00.000000
##
## bien_afectado
## El patrimonio :124338
## La familia : 63044
## La libertad y la seguridad sexual: 14863
## La vida y la integridad corporal : 48391
##
##
##
Se propone el siguiente modelo de randomForest para
predecir la colonia a la que puede pertenecer un delito sin
colonia registrada, usando las variables limpias y procesadas en los
pasos anteriores. Primero, se fija una semilla aleatoria para asegurar
la reproducibilidad del modelo. Se entrena el modelo con 100 árboles
usando todos los predictores disponibles en
cleanData para predecir la variable
colonia. Después, se selecciona aleatoriamente un 20%
de los datos para evaluar el desempeño del modelo, realizando una
predicción sobre esta muestra de prueba. Se calcula la
precisión del modelo como la proporción de
predicciones correctas en el conjunto de test y se imprime este
resultado en porcentaje. Finalmente, el modelo entrenado se guarda en
dos formatos diferentes para su posterior uso o análisis, y se muestra
cómo cargar el modelo guardado desde un archivo.
# Entrenamiento de modelo randomForest para predecir colonia
# set.seed(123)
# modelo_colonia <- randomForest(colonia ~ .,
# data = cleanData,
# ntree = 100,
# importance = TRUE)
# Leer modelo
modelo_colonia <- readRDS("Modelo_Predictor_de_Colonia.rds")
# Selección de muestra de test
set.seed(42)
indice_test <- sample(1:nrow(cleanData), size = 0.2 * nrow(cleanData))
data_test_sample <- cleanData[indice_test, ]
# Predicción con el modelo ya entrenado
pred_test <- predict(modelo_colonia, newdata = data_test_sample)
# Calcular precisión
accuracy <- mean(pred_test == data_test_sample$colonia)
print(paste("Precisión de muestra de test:", round(accuracy * 100, 2), "%"))
## [1] "Precisión de muestra de test: 67.38 %"
# Guardar modelo en un archivo
saveRDS(modelo_colonia, "Modelo_Predictor_de_Colonia.rds")
save(modelo_colonia, file = "Modelo_Predictor_de_Colonia.RData")
Una vez que limpiamos los datos, pasemos a ver algunas gráficas interesantes:
cleanData %>%
filter(!is.na(colonia)) %>%
count(colonia, sort = TRUE) %>%
slice_max(n, n = 20) %>%
ggplot(aes(x = reorder(colonia, n), y = n)) +
geom_col(fill = "firebrick") +
coord_flip() +
labs(title = "Colonias más violentas", x = "Colonia", y = "Número de delitos")
pal <- colorFactor(palette = "Set1", domain = cleanData$delito)
# Crear el mapa
cleanData %>%
filter(!is.na(x) & !is.na(y)) %>%
leaflet() %>%
addTiles() %>%
addCircles(
lng = ~ x,
lat = ~ y,
radius = 20,
color = ~ pal(delito),
stroke = FALSE,
fillOpacity = 0.5,
label = ~ delito
) %>%
addLegend(
"bottomright",
pal = pal,
values = ~ delito,
title = "Tipo de Delito"
)